import akshare as ak
import schedule
import time
import requests
import urllib.parse
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr
import ssl
import socket

# ========== 配置区域 ==========
# 你要监控的基金代码（可以写多个）
FUND_CODES = ['018994','018957','016371','017103','002112','001407','018123','022365','021528']

# 推送方式选择: "bark" 或 "serverchan" 或 "email"
PUSH_TYPE = "bark"
# PUSH_TYPE = "serverchan"
# PUSH_TYPE = "email"

# Bark 配置（如果用 Bark）
BARK_URL = "https://api.day.app/J7HKetWu52MRacqYvCqrWA/"

# Server酱配置（如果用微信）
SERVERCHAN_KEY = "SCT294188T2C4EF08VZgG32AxMaWSGHz6w"  # 从 https://sct.ftqq.com/ 获取

# Email 配置（QQ邮箱示例）
EMAIL_SMTP_SERVER = "smtp.qq.com"
EMAIL_SMTP_PORT = 465
EMAIL_FROM = "3233473538@qq.com"
EMAIL_TO = ["3233473538@qq.com"]
EMAIL_PASSWORD = "hvcjoasqynmibceg"
EMAIL_DEBUG = False  # 如需排查可改为 True
# ========== 配置结束 ==========


def fetch_fund_info():
    """获取关注基金的实时估值（返回 DataFrame）"""
    try:
        df = ak.fund_value_estimation_em(symbol="全部")
        if df is None or df.empty:
            return df
        filtered = df.query('基金代码 in @FUND_CODES').reset_index(drop=True)
        print(filtered)
        return filtered
    except Exception as e:
        # 出错时返回包含错误信息的 DataFrame，方便后续统一处理
        import pandas as pd
        return pd.DataFrame({
            "错误": [str(e)]
        })


def send_email(subject, content):
    """通过邮箱发送通知（QQ邮箱，优先 SSL 465，失败回退 STARTTLS 587）"""
    try:
        msg = MIMEText(content, "plain", "utf-8")
        msg["From"] = formataddr((str(Header("基金提醒", "utf-8")), EMAIL_FROM))
        msg["To"] = ", ".join(EMAIL_TO)
        msg["Subject"] = Header(subject, "utf-8")

        context = ssl.create_default_context()

        # 先做连通性测试，便于区分网络/握手问题
        try:
            with socket.create_connection((EMAIL_SMTP_SERVER, 465), timeout=5):
                pass
        except Exception as conn_ssl_err:
            print(f"无法连接 smtp.qq.com:465 -> {conn_ssl_err}")
        try:
            with socket.create_connection((EMAIL_SMTP_SERVER, 587), timeout=5):
                pass
        except Exception as conn_tls_err:
            print(f"无法连接 smtp.qq.com:587 -> {conn_tls_err}")

        # 优先使用 SSL 465 端口（重试最多 2 次）
        last_err = None
        for attempt in range(2):
            try:
                with smtplib.SMTP_SSL(EMAIL_SMTP_SERVER, 465, context=context, timeout=25) as server:
                    if EMAIL_DEBUG:
                        server.set_debuglevel(1)
                    server.ehlo()
                    server.login(EMAIL_FROM, EMAIL_PASSWORD)
                    server.sendmail(EMAIL_FROM, EMAIL_TO, msg.as_string())
                    return
            except Exception as e_ssl:
                last_err = e_ssl
                print(f"SMTP_SSL 465 尝试 {attempt+1}/2 失败: {e_ssl}")

        print("尝试切换到 STARTTLS 587 ...")
        # 回退使用 STARTTLS 587（重试最多 2 次）
        for attempt in range(2):
            try:
                with smtplib.SMTP(EMAIL_SMTP_SERVER, 587, timeout=25) as server:
                    if EMAIL_DEBUG:
                        server.set_debuglevel(1)
                    server.ehlo()
                    server.starttls(context=context)
                    server.ehlo()
                    server.login(EMAIL_FROM, EMAIL_PASSWORD)
                    server.sendmail(EMAIL_FROM, EMAIL_TO, msg.as_string())
                    return
            except Exception as e_tls:
                last_err = e_tls
                print(f"STARTTLS 587 尝试 {attempt+1}/2 失败: {e_tls}")

        # 若到此仍未成功
        raise last_err if last_err else RuntimeError("未知的邮件发送错误")
    except Exception as e:
        print(f"邮件发送失败: {e}")


def send_notification(msg):
    """发送通知"""
    if PUSH_TYPE == "bark":
        # 对内容进行 URL 编码，避免特殊字符导致请求失败
        encoded = urllib.parse.quote(msg)
        requests.get(BARK_URL + encoded)
    elif PUSH_TYPE == "serverchan":
        url = f"https://sctapi.ftqq.com/{SERVERCHAN_KEY}.send"
        requests.post(url, data={"title": "基金提醒", "desp": msg})
    elif PUSH_TYPE == "email":
        send_email("基金提醒", msg)
    else:
        print("未配置有效的推送方式")
    print("通知已发送:", msg)


def job():
    """定时任务：抓取关注基金信息并推送"""
    df = fetch_fund_info()
    # 统一格式化输出
    lines = []
    from datetime import datetime
    now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    # fund_now_str = datetime.now().strftime("%Y-%m-%d")

    if df is None or df.empty:
        lines.append(f"[{now_str}] 未找到匹配基金数据或获取失败。")
    else:
        # 常见列名（不同版本的 akshare 可能稍有差异）
        code_col = "基金代码" if "基金代码" in df.columns else "代码"
        name_col = "基金名称"
        # est_nv_col = "估算净值" if "估算净值" in df.columns else None
        # pct_col = fund_now_str+"-"+"估算数据-估算增长率" if "估算涨跌幅" in df.columns else ("涨跌幅" if "涨跌幅" in df.columns else None)
        # pct_col = "2025-08-28-估算数据-估算增长率"
        pct_col = datetime.now().strftime("%Y-%m-%d")+"-估算数据-估算增长率"
        # time_col = "估算时间" if "估算时间" in df.columns else ("时间" if "时间" in df.columns else None)
        # unit_nv_col = "单位净值" if "单位净值" in df.columns else None

        lines.append(f"[{now_str}] 基金实时估值")

        for _, row in df.iterrows():
            code = str(row.get(code_col, "-"))
            name = str(row.get(name_col, "-")) if name_col else "-"
            print("name:"+name)
            # est_nv = row.get(est_nv_col, "-") if est_nv_col else "-"
            pct = row.get(pct_col, "-") if pct_col else "-"
            # unit_nv = row.get(unit_nv_col, "-") if unit_nv_col else "-"
            # est_time = row.get(time_col, "-") if time_col else "-"
            try:
                if isinstance(pct, (int, float)):
                    pct = f"{pct:.2f}%"
            except Exception:
                pass
            # lines.append(f"{code} {name} | 估值:{est_nv} 涨跌:{pct} 净值:{unit_nv} @ {est_time}")
            lines.append(f"{code} {name} {pct}")

    msg = "\n".join(lines)
    print(msg)
    send_notification(msg)


def main():
    # 每 10 分钟执行一次
    schedule.every(10).minutes.do(job)
    print("基金监控已启动，每10分钟检查一次...")

    # 立即执行一次
    job()

    while True:
        schedule.run_pending()
        time.sleep(1)


if __name__ == "__main__":
    main()